package sostats.core.base;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;

import sostats.core.datasource.Configuration;
import sostats.core.help.SostatsException;
import sostats.core.help.Tuple;
import sostats.core.metadata.Describable;
import sostats.core.metadata.Dictionary;
import sostats.core.metadata.Metadata;
import sostats.core.query.Dataset;
import sostats.core.query.Query;
import sostats.core.query.QueryExecutor;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

public class StatisticalContext {
	public static final String CONNECTOR ="#";
	private final Configuration config; 
	private final Connection connection;
	private final Dictionary dictionary;
	private HashMap<String,Table> tables;
	private HashMap<String,String> tableUnion;
	public Connection getConnection(){
		return connection;
	}
	public String getDescription(final String metadataName){
		Describable describable = new Describable() {
			
			@Override
			public String getName() {
				return metadataName;
			}
		};
		return dictionary.lookUp(describable).getDescription();
	}
	public StatisticalContext(Configuration config) throws SostatsException{
		try{
			Class.forName(config.getDataSource().getDriver());
			connection = DriverManager.getConnection(
					config.getDataSource().getUrl(),
					config.getDataSource().getUserName(),
					config.getDataSource().getPassword());
			this.config = config;
			tableUnion = new HashMap<String, String>();
			tables = new HashMap<String, Table>();
			dictionary = new Dictionary();
		}catch(Exception e ){
			throw new SostatsException(e);
		}
	}
	public void setTablePK(String tableName, String pk){
		Table table = tables.get(tableName);
		if(table==null) return ;
		table.setPK(pk);
	}
	public void registryMetadata(String name, String description){
		dictionary.addItem(name, description);
	}
	public List<Metadata> getTables(){
		return Lists.transform(Lists.newArrayList(tables.values()), 
				new Function<Table, Metadata>() {
					@Override
					public Metadata apply(Table input) {
						return dictionary.lookUp(input);
					}
				});
	}
	public List<Metadata> getSummaries(String table){
		Table target = tables.get(table);
		if(target==null) return null;
		return Lists.transform(
				target.getSummaries(),
				new AttributeToMetaData(target));
	}
	public List<Metadata> getCategories(String table){
		Table target = tables.get(table);
		if(target==null) return null;
		return Lists.transform(
				target.getCategories(),
				new AttributeToMetaData(target));
	}
	Joint getJoint(String table1,String table2){
		Table master = tables.get(table1);
		return Iterables.getOnlyElement(master.getJoint(table2));
	}
	public void addTable(String name) throws SostatsException{
		if(!isTableExisted(name)&&validateTable(name)){
			Table table = new Table(name);
			tables.put(name, table);
			tableUnion.put(name, name);
		}
	}
	private int depth(String table){
		int depth=1;
		while(!isRoot(table)){
			depth++;
			table = tableUnion.get(table);
		}
		return depth;
	}
	private boolean isRoot(String table){
		if(!tableUnion.containsKey(table)) return false;
		return table.equals(tableUnion.get(table));
	}
	private String getLeastCommonAncestor(String table1, String table2){
		if(table1==null) return table2;
		if(table2==null) return table1;
		if(table1.equals(table2)) return table1;
		int depth1 = depth(table1);
		int depth2 = depth(table2);
		while(depth1>depth2) {
			table1 = tableUnion.get(table1);
			depth1--;
		}
		while(depth2>depth1) {
			table2 = tableUnion.get(table2);
			depth2--;
		}
		while(!table1.equals(table2)){
			if(isRoot(table1)||isRoot(table2)) return null;
			table1 = tableUnion.get(table1);
			table2 = tableUnion.get(table2);
		}
		return table1;
	}
	public String getDistinctKey(String tableName){
		Table table = tables.get(tableName);
		String pk = table.getPK();
		if(pk==null) return null;
		return tableName+CONNECTOR+pk;
	}
	public boolean isTableJoined(String table1, String table2){
		String root = getLeastCommonAncestor(table1, table2);
		return root!=null;
	}
	public HashMap<String, String> getJoiningTables(List<String> tables) {
		HashMap<String, String> res = new HashMap<String, String>();
		if(tables.size()==0) return res;
		String root = null;
		for(String table:tables){
			root = getLeastCommonAncestor(root, table);
		}
		for(String table:tables){
			while(!isRoot(table)&&!table.equals(root)&&!res.containsKey(table)){
				String parent = tableUnion.get(table);
				res.put(table, parent);
				table = parent;
			}
		}
		res.put(root, root);
		return res;
	}
	public Tuple<String, String> getJoiningKeys(String table1, String table2) {
		Table master = tables.get(table1);
		return Iterables.getOnlyElement(master.getJoint(table2));
	}

	public void addJoint(String table1, String key1,
			String table2, String key2) throws SostatsException{
		if(isTableExisted(table1)&&validateAttribute(key1, table1)
				&&isTableExisted(table2)&&validateAttribute(key2, table2)){
			if(isTableJoined(table1, table2)) return ;
			Table master = tables.get(table1);
			Table slaff = tables.get(table2);
			master.addJointTable(key1, slaff, key2);
			tableUnion.put(table2, table1);
		}
	}
	public void addCondition(String condition,String table) throws SostatsException{
		if(isTableExisted(table)&&validateAttribute(condition,table)){
			Table target = tables.get(table);
			target.addCondtion(condition);
		}
	}
	public void addCategory(String category, String table) throws SostatsException{
		if(isTableExisted(table)&&validateAttribute(category,table)){
			Table target = tables.get(table);
			target.addCategory(category);
		}
	}
	public void addSummary(String summary, String table ) throws SostatsException{
		if(isTableExisted(table)&&validateAttribute(summary,table)){
			Table target = tables.get(table);
			target.addSummary(summary);
		}
	}
	public Dataset doQuery(Query query) throws SostatsException{
		try{
			return QueryExecutor.doQuery(query, this);
		}catch(SQLException e){
			throw new SostatsException(e);
		}
	}
	
	private boolean isTableExisted(String table){
		return tables.containsKey(table);
	}
	private boolean validateTable(String name) throws SostatsException{
		try {
			DatabaseMetaData metaData = connection.getMetaData();
			ResultSet rs = metaData.getTables(config.getCatalog(), config.getSchema(), name.toUpperCase(),null);
			return rs.next();
		} catch (SQLException e) {
			throw new SostatsException(e);
		}
	}
	private boolean validateAttribute(String name,String table) throws SostatsException{
		try {
			DatabaseMetaData metaData = connection.getMetaData();
			ResultSet rs = metaData.getColumns(config.getCatalog(), config.getSchema(), table.toUpperCase(),name.toUpperCase());
			return rs.next();
		} catch (SQLException e) {
			throw new SostatsException(e);
		}
	}
	private class AttributeToMetaData implements Function<Attribute,Metadata>{
		private final Table table;
		public AttributeToMetaData(Table table) {
			this.table = table;
		}
		@Override
		public Metadata apply(Attribute input) {
			Attribute withTable = new Attribute(table.getName()+CONNECTOR+input.getName());
			Metadata md = dictionary.lookUp(withTable);
			return md;
		}
		
	}
	public List<Metadata> getConditions(String table) {
		Table target = tables.get(table);
		if(target==null) return null;
		return Lists.transform(
				target.getConditions(),
				new AttributeToMetaData(target));
	}
}
